package com.egov.config.redis;

import com.egov.common.constant.CommonConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

/**
 * Redis缓存配置类
 *
 * @Author denggang
 * @Date 2019/05/10 16:11
 * 使用说明
 * 在Service或Mapper对应方法上添加如下注解即可
 * 通用属性解释：
 * value属性：要使用缓存的名称
 * key属性：使用SpEL表达式自定义缓存Key，
 * 例如：#name—以参数name作为自定义缓存Key，
 * #result.name—以返回值结果的name属性作为自定义缓存Key
 * #p0" 指定传入的第一个参数作为定义缓存Key
 * <p>
 * CacheConfig（cacheNames = "users"）注解在类上，统一该类下的缓存名字为"users"
 * <p>
 * Cacheable 方法级注解，将查询结果缓存到redis中，如果没有缓存则会执行方法并将返回值缓存，如果有缓存时，不会执行方法而是直接返回缓存中的值
 * <p>
 * 　　CachePut 方法级注解，不管有没有缓存都会执行方法并将结果缓存起来（用于更新缓存）
 * <p>
 * 　　CacheEvict 方法级注解，指定key，删除缓存数据，allEntries=true,方法调用后将立即清除缓存
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

    Logger logger = LoggerFactory.getLogger(RedisConfig.class);

    /**
     * 集群配置 需要时配置
     * @return
     */
    // @Bean
//    public JedisCluster jedisCluster() {
  //
//        Set<HostAndPort> nodeSet = new HashSet<>();
//        for (String node : redisProperties.getCluster().getNodes()) {
//            String[] split = node.split(":");
//            nodeSet.add(new HostAndPort(split[0], Integer.valueOf(split[1])));
//        }
//        return new JedisCluster(nodeSet, redisProperties.getCluster().getTimeout(), redisProperties.getCluster().getSoTimeout(), redisProperties.getCluster().getMaxAttempts(), redisProperties.getCluster().getPassword(), new GenericObjectPoolConfig());//需要密码连接的创建对象方式
//    }


    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        //  设置自动key的生成规则，配置spring boot的注解，进行方法级别的缓存
        // 使用：进行分割，可以很多显示出层级关系
        // 这里其实就是new了一个KeyGenerator对象，只是这是lambda表达式的写法，我感觉很好用，大家感兴趣可以去了解下
        return (target, method, params) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getName());
            sb.append(":");
            sb.append(method.getName());
            for (Object obj : params) {
                sb.append(":" + String.valueOf(obj));
            }
            String rsToUse = String.valueOf(sb);
            logger.info("自动生成Redis Key -> [{}]", rsToUse);
            return rsToUse;
        };
    }

    @Bean
    @Primary //当有多个管理器的时候，必须使用该注解在一个管理器上注释：表示该管理器为默认的管理器
    public CacheManager cacheManager(LettuceConnectionFactory lettuceConnectionFactory) {
        // 初始化缓存管理器，在这里我们可以设置缓存的整体过期时间什么的
        logger.info("初始化 -> [{}]", "CacheManager RedisCacheManager Start");
        RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager
                .RedisCacheManagerBuilder
                .fromConnectionFactory(lettuceConnectionFactory);
        //设置@cacheable 序列化方式(FastJsonRedisSerializer),设置缓存过期时间 3天 单位秒
        RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new FastJsonRedisSerializer(Object.class)))
                .entryTtl(Duration.ofSeconds(CommonConstant.REDIS_EXPIRE));
        builder.cacheDefaults(cacheConfiguration);
        return builder.build();
    }


    /**
     * RedisTemplate配置
     设置序列化工具，用FastJsonRedisSerializer序列化的话，被序列化的对象不用实现Serializable接口
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
        //设置序列化
        FastJsonRedisSerializer fFastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);

        // 配置redisTemplate
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
        redisTemplate.setConnectionFactory(lettuceConnectionFactory);
        RedisSerializer<?> stringSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringSerializer); // key序列化
        redisTemplate.setValueSerializer(fFastJsonRedisSerializer); // value序列化
        redisTemplate.setHashKeySerializer(stringSerializer); // Hash key序列化
        redisTemplate.setHashValueSerializer(fFastJsonRedisSerializer); // Hash value序列化
        //设置其他默认的序列化方式为fastjson
        redisTemplate.setDefaultSerializer(fFastJsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    @Override
    @Bean
    public CacheErrorHandler errorHandler() {
        // 异常处理，当Redis发生异常时，打印日志，但是程序正常走
        logger.info("初始化 -> [{}]", "Redis CacheErrorHandler");
        CacheErrorHandler cacheErrorHandler = new CacheErrorHandler() {
            @Override
            public void handleCacheGetError(RuntimeException e, Cache cache, Object key) {
                logger.error("Redis occur handleCacheGetError：key -> [{}]", key, e);
            }

            @Override
            public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {
                logger.error("Redis occur handleCachePutError：key -> [{}]；value -> [{}]", key, value, e);
            }

            @Override
            public void handleCacheEvictError(RuntimeException e, Cache cache, Object key) {
                logger.error("Redis occur handleCacheEvictError：key -> [{}]", key, e);
            }

            @Override
            public void handleCacheClearError(RuntimeException e, Cache cache) {
                logger.error("Redis occur handleCacheClearError：", e);
            }
        };
        return cacheErrorHandler;
    }

}

